import { wait } from '@/utils';
import { del, get, keys, set } from 'idb-keyval';

let initialized = false;
let go: Go;
let goExitPromise: Promise<void> | undefined;

const CACHE_KEY = 'openim-wasm-cache';

export async function initializeWasm(url: string): Promise<Go | null> {
  if (initialized) {
    return null;
  }

  if (typeof window === 'undefined') {
    return Promise.resolve(null);
  }

  go = new Go();
  let wasm;
  try {
    const start = Date.now();
    if ('instantiateStreaming' in WebAssembly) {
      console.info('SDK => wasm initialize by instantiateStreaming', url);
      wasm = await WebAssembly.instantiateStreaming(
        fetchIndexedDBCache(url, start),
        go.importObject
      );
    } else {
      console.info('SDK => wasm initialize by instantiate', url);
      const bytes = await fetchIndexedDBCache(url, start).then(resp =>
        resp.arrayBuffer()
      );
      wasm = await WebAssembly.instantiate(bytes, go.importObject);
    }
    go.run(wasm.instance);
  } catch (error) {
    console.error('SDK => wasm initialize failed:', error);
    return null;
  }

  await wait(100);
  initialized = true;
  return go;
}

export function reset() {
  initialized = false;
}

export function getGO() {
  return go;
}

export function getGoExitPromise() {
  return goExitPromise;
}

async function fetchWithCache(url: string): Promise<Response> {
  const start = Date.now();

  if (!('caches' in window)) {
    // 从IndexedDB加载SDK
    return fetchIndexedDBCache(url, start);
  }

  // 从CacheStorage加载SDK
  const resourceUpdated = async () => {
    const serverResponse = await fetch(url, { method: 'HEAD' });
    const etag = serverResponse.headers.get('ETag');
    const lastModified = serverResponse.headers.get('Last-Modified');
    return (
      serverResponse.ok &&
      (etag !== cachedResponse?.headers.get('ETag') ||
        lastModified !== cachedResponse?.headers.get('Last-Modified'))
    );
  };

  const cache = await caches.open(CACHE_KEY);
  const cachedResponse = await cache.match(url);
  if (cachedResponse && !(await resourceUpdated())) {
    const task = Date.now() - start;
    console.info('SDK => wasm loading from caches cost', task, url);
    return cachedResponse;
  }

  return fetchAndUpdateCache(url, cache, start);
}

async function fetchAndUpdateCache(
  url: string,
  cache: Cache,
  start: number
): Promise<Response> {
  const response = await fetch(url, { cache: 'no-cache' });
  try {
    await cache.put(url, response.clone());
  } catch (error) {
    console.warn('SDK => wasm failed to put cache');
  }
  const task = Date.now() - start;
  console.info('SDK => wasm loading from caches fetch cost', task, url);
  return response;
}

async function fetchIndexedDBCache(
  url: string,
  start: number
): Promise<Response> {
  // 删除IndexedDB旧缓存
  keys().then(items => {
    items.forEach(itk => {
      if (url === itk.toString()) {
        console.info('SDK => idb match cache', itk);
      } else {
        del(itk.toString()).then(() =>
          console.info('SDK => idb del cache', itk)
        );
      }
    });
  });

  // 存取IndexedDB缓存
  let bytes = await get(url);
  let cache;
  if (bytes) {
    const task = Date.now() - start;
    console.info('SDK => wasm loading from idb cache cost', task, url);
    cache = false;
  } else {
    bytes = await fetch(url).then(resp => resp.arrayBuffer());
    const task = Date.now() - start;
    console.info('SDK => wasm loading from idb fetch cost', task, url);
    cache = true;
  }
  if (cache) {
    set(url, bytes);
    console.info('SDK => idb set wasm cache', url);
  }

  // arrayBuffer缓存创建fetch响应对象
  const headers = new Headers();
  headers.append('Content-Type', 'application/wasm');
  const response = new Response(bytes, {
    status: 200, // 可选，默认为200
    statusText: 'OK', // 可选，默认为'OK'
    headers: headers,
  });
  const task = Date.now() - start;
  console.info('SDK => wasm build response cost', task, url);
  return response;
}
